﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace DVRMS_Renamer
{
    public class SeriesList
    {
        private List<Series> colSeries = new List<Series>();

        public void Add(Series series)
        {
            colSeries.Add(series);
        }

        public void Clear()
        {
            colSeries = new List<Series>();
        }

        public int Count
        {
            get
            {
                return colSeries.Count;
            }
        }

        public Series Item(int i)
        {
            return colSeries[i];
        }

        public Series Find(string SeriesName)
        {
            Series series = null;

            series = colSeries.Find(
                delegate(Series n)
                {
                    return n.seriesName.ToLower() == SeriesName.ToLower();
                });

            return series;
        }

        public MySeriesEnumerator GetEnumerator()
        {
            return new MySeriesEnumerator(this);
        }
    }

    public class Series
    {
        private List<Episode> colEpisode = new List<Episode>();

        public string seriesName;
        public DateTime originalBroadcast;
        public string subTitleDescription;
        public string url;
        public string urlCaption;

        public void EpisodeAdd(Episode episode)
        {
            colEpisode.Add(episode);
        }

        public Episode MatchEpisode(DateTime originalBroadcast, string subTitle, string subTitleDescription)
        {
            List<Episode> colMatchingEpisodes = new List<Episode>();
            Episode matchedEpisode = null;

            // Build up a match list with a confidence for each match
            // then pick the result with the highest confidence 0-100
            for (int i = 0; i < colEpisode.Count; i++)
            {
                Episode currentEpisode = colEpisode[i];
                Episode tempMatchedEpisode = colEpisode[i];

                // Try to match by exact episode name
                if (!string.IsNullOrEmpty(subTitle) && !string.IsNullOrEmpty(currentEpisode.EpisodeName))
                {
                    if (string.Compare(currentEpisode.EpisodeName.Trim(), subTitle.Trim(), true) == 0)
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            100);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                    else if (string.Compare(currentEpisode.EpisodeName.Trim().Replace(" ", ""), subTitle.Trim().Replace(" ", ""), true) == 0)
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            100);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                    else if (currentEpisode.EpisodeName.ToUpper().Trim().Contains(subTitle.Trim().ToUpper()))
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                    else if (subTitle.Trim().ToUpper().Contains(currentEpisode.EpisodeName.Trim().ToUpper()))
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // Try to match by original broadcast date
                if (originalBroadcast != null && originalBroadcast != new DateTime())
                {
                    if (currentEpisode.FirstAired == originalBroadcast)
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            100);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // Try to match by episode description
                if (matchedEpisode == null)
                {
                    if (currentEpisode.Overview == subTitleDescription)
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            100);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // http://stackoverflow.com/questions/5859561/getting-the-closest-string-match
                if (currentEpisode.Overview.Length > 0 && subTitleDescription.Length > 0)
                {
                    decimal phraseWeight = 0.5M;
                    decimal wordsWeight = 1.0M;
                    decimal minWeight = 10M;
                    decimal maxWeight = 1.0M;
                    decimal lengthWeight = -0.3M;
                    decimal phraseValue = valuePhrase(subTitleDescription, currentEpisode.Overview);
                    decimal wordsValue = valueWords(subTitleDescription, currentEpisode.Overview);
                    //decimal lengthValue = Math.Max(subTitleDescription.Length, currentEpisode.Overview.Length);
                    decimal lengthValue = (subTitleDescription.Length + currentEpisode.Overview.Length);

                    decimal value = Math.Min(phraseWeight * phraseValue, wordsWeight * wordsValue) * minWeight +
                        Math.Max(phraseWeight * phraseValue, wordsWeight * wordsValue) * maxWeight + lengthWeight * lengthValue;

                    if (value < 500)
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // Try to match the first part of the description upto a colon
                if (subTitleDescription.Contains(":"))
                {
                    if (subTitleDescription.Substring(0, subTitleDescription.IndexOf(":")).ToLower()
                        == currentEpisode.EpisodeName.ToLower())
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // Try to match the first part of the description upto a full stop
                if (subTitleDescription.Contains(".") && subTitleDescription.IndexOf(".") != subTitleDescription.Length)
                {
                    if (subTitleDescription.Substring(0, subTitleDescription.IndexOf(".")).ToLower()
                        == currentEpisode.EpisodeName.ToLower())
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // Try to match the first part of the description upto a comma
                if (subTitleDescription.Contains(","))
                {
                    if (subTitleDescription.Substring(0, subTitleDescription.IndexOf(",")).ToLower()
                        == currentEpisode.EpisodeName.ToLower())
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // Try to match the first part of the description upto a question mark
                if (subTitleDescription.Contains("?") && subTitleDescription.IndexOf("?") != subTitleDescription.Length)
                {
                    if (subTitleDescription.Substring(0, subTitleDescription.IndexOf("?") + 1).ToLower()
                        == currentEpisode.EpisodeName.ToLower())
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // Also try to match the first part of the description upto a colon without any puctuation
                if (subTitleDescription.Contains(":"))
                {
                    string subString = subTitleDescription.Substring(0, subTitleDescription.IndexOf(":")).ToLower();
                    subString.Replace(",", " ");
                    subString.Replace(".", " ");
                    subString.Replace(";", " ");
                    subString.Replace(":", " ");
                    subString.Replace("?", " ");
                    subString.Replace("!", " ");
                    subString.Replace("'", " ");
                    subString.Replace("\"", " ");
                    subString.Replace("@", " ");
                    subString.Replace("#", " ");
                    subString.Replace("*", " ");
                    subString.Replace("&", " ");
                    subString.Replace("^", " ");
                    subString.Replace("%", " ");
                    subString.Replace("$", " ");
                    subString.Replace("£", " ");
                    subString.Replace("(", " ");
                    subString.Replace(")", " ");
                    subString.Replace("[", " ");
                    subString.Replace("]", " ");
                    subString.Replace("{", " ");
                    subString.Replace("}", " ");
                    subString.Replace(">", " ");
                    subString.Replace("<", " ");
                    subString.Replace(" / ", " ");
                    subString.Replace("/ ", " ");
                    subString.Replace("/", " ");
                    subString.Replace(" \\ ", " ");
                    subString.Replace("\\ ", " ");
                    subString.Replace("\\", " ");
                    subString.Replace("  ", " ");

                    if (subString == currentEpisode.EpisodeName.ToLower())
                    {
                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                            currentEpisode.FirstAired, currentEpisode.Overview,
                            currentEpisode.URL, currentEpisode.URLCaption,
                            90);
                        colMatchingEpisodes.Add(tempMatchedEpisode);
                    }
                }

                // See if the episode title appears in the first 50 characters of the description
                if (matchedEpisode == null)
                {
                    if (subTitleDescription.Length > 50 && currentEpisode.EpisodeName.Trim() != "")
                    {
                        if (subTitleDescription.Substring(0, 49).Contains(currentEpisode.EpisodeName))
                        {
                            tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                                currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                                currentEpisode.FirstAired, currentEpisode.Overview,
                                currentEpisode.URL, currentEpisode.URLCaption,
                                80);
                            colMatchingEpisodes.Add(tempMatchedEpisode);
                        }

                        // Try with alternate special characters
                        string strEpisode = currentEpisode.EpisodeName;
                        strEpisode = strEpisode.Replace("&", "and");
                        if (subTitleDescription.Substring(0, 49).Contains(strEpisode))
                        {
                            tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                                currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                                currentEpisode.FirstAired, currentEpisode.Overview,
                                currentEpisode.URL, currentEpisode.URLCaption,
                                80);
                            colMatchingEpisodes.Add(tempMatchedEpisode);
                        }
                    }
                }


                // Try to match using the first 30 characters from the DVRMS files description
                if (matchedEpisode == null)
                {
                    if (subTitleDescription.Length > 30 && currentEpisode.Overview.Trim() != "")
                    {
                        if (currentEpisode.Overview.Contains(subTitleDescription.Substring(0, 29)))
                        {
                            tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                                currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                                currentEpisode.FirstAired, currentEpisode.Overview,
                                currentEpisode.URL, currentEpisode.URLCaption,
                                75);
                            colMatchingEpisodes.Add(tempMatchedEpisode);
                        }
                    }
                }

                // Try to break down the description into sentences and check 
                // the first 30 characters from each sentence
                if (matchedEpisode == null && subTitleDescription.Contains("."))
                {
                    string[] strSeparator = new string[] { "." };
                    string[] strSentences = subTitleDescription.Split(strSeparator,
                        StringSplitOptions.RemoveEmptyEntries);

                    // Confidence is also based on sentence position matching
                    // ie. starting confience for this level is 70%
                    // -1 * the difference between the source and destination sentence.
                    int intPara = 0;
                    foreach (string strPara in strSentences)
                    {
                        intPara += 1;

                        if (strPara.Length > 30)
                        {
                            string[] strEpisodeSentences = currentEpisode.Overview.Split(strSeparator,
                                StringSplitOptions.RemoveEmptyEntries);

                            int intEpisodePara = 0;
                            foreach (string strEpisodePara in strEpisodeSentences)
                            {
                                intEpisodePara += 1;
                                if (strEpisodePara.Length > 30)
                                {
                                    if (strEpisodePara.Trim().Substring(0, 29).ToLower() == strPara.Trim().Substring(0, 29).ToLower())
                                    {
                                        // Work out which sentence we found the search string in
                                        tempMatchedEpisode = new Episode(currentEpisode.SeasonNumber,
                                            currentEpisode.EpisodeNumber, currentEpisode.EpisodeName,
                                            currentEpisode.FirstAired, currentEpisode.Overview,
                                            currentEpisode.URL, currentEpisode.URLCaption,
                                            70 + (-1 * Math.Abs(intEpisodePara - intPara)));

                                        colMatchingEpisodes.Add(tempMatchedEpisode);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Now find the match with the highest confidence
            int intBestConfidence = 0;
            for (int i = 0; i < colMatchingEpisodes.Count; i++)
            {
                if (colMatchingEpisodes[i].Confidence > intBestConfidence)
                {
                    matchedEpisode = colMatchingEpisodes[i];
                    intBestConfidence = colMatchingEpisodes[i].Confidence;
                }
            }

            return matchedEpisode;
        }

        private decimal valuePhrase(string s1, string s2)
        {
            return LevenshteinDistance(s1, s2);
        }

        private decimal valueWords(string s1, string s2)
        {
            string[] words1 = SplitMultiDelims(s1, " _-");
            string[] words2 = SplitMultiDelims(s2, " _-");

            int word1, word2;
            decimal thisD, wordbest;
            decimal wordsTotal = 0;

            for (word1 = 0; word1 < words1.Length; word1++)
            {
                wordbest = s2.Length;

                for (word2 = 0; word2 < words2.Length; word2++)
                {
                    thisD = LevenshteinDistance(words1[word1], words2[word2]);
                    if (thisD < wordbest) wordbest = thisD;
                    if (thisD == 0) break;
                }

                wordsTotal += wordbest;
            }
            return wordsTotal;
        }

        /// <summary>
        /// This function splits Text into an array of substrings, each substring
        /// delimited by any character in DelimChars. Only a single character
        /// may be a delimiter between two substrings, but DelimChars may
        /// contain any number of delimiter characters. It returns a single element
        /// array containing all of text if DelimChars is empty, or a 1 or greater
        /// element array if the Text is successfully split into substrings.
        /// If IgnoreConsecutiveDelimiters is true, empty array elements will not occur.
        /// If Limit greater than 0, the function will only split Text into 'Limit'
        /// array elements or less. The last element will contain the rest of Text.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="DelimChars"></param>
        /// <returns></returns>
        private string[] SplitMultiDelims(string text, string delimChars)
        {
            int elemStart = 0;
            int lDelims, lText;
            List<string> arr = new List<string>();

            lText = text.Length;
            lDelims = delimChars.Length;

            if (lDelims == 0 || lText == 0)
            {
                arr.Add(text);
                return arr.ToArray();
            }

            for (int n = 0; n < lText; n++)
            {
                if (delimChars.Contains(text.Substring(n, 1)))
                {
                    arr.Add(text.Substring(elemStart, n - elemStart));

                    elemStart = n + 1;
                }
            }

            return arr.ToArray();
        }

        private int LevenshteinDistance(string s, string t)
        {
            int n = s.Length;
            int m = t.Length;
            int[,] d = new int[n + 1, m + 1];

            // Step 1
            if (n == 0) { return m; }
            if (m == 0) { return n; }

            // Step 2
            for (int i = 0; i <= n; d[i, 0] = i++) { }
            for (int j = 0; j <= m; d[0, j] = j++) { }

            // Step 3
            for (int i = 1; i <= n; i++)
            {
                //Step 4
                for (int j = 1; j <= m; j++)
                {
                    // Step 5
                    int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;

                    // Step 6
                    d[i, j] = Math.Min(
                        Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
                        d[i - 1, j - 1] + cost);
                }
            }
            // Step 7
            return d[n, m];
        }
    }

    public class Episode
    {
        private int mSeasonNumber;
        private int mEpisodeNumber;
        private string mEpisodeName;
        private DateTime mFirstAired;
        private string mOverview;
        private string mURL;
        private string mURLCaption;
        private int mConfidence;

        public Episode(int seasonNumber, int episodeNumber, string episodeName, 
            DateTime firstAired, string overview, string url, string urlCaption)
        {
            mSeasonNumber = seasonNumber;
            mEpisodeNumber = episodeNumber;
            mEpisodeName = episodeName;
            mFirstAired = firstAired;
            mOverview = overview;
            mURL = url;
            mURLCaption = urlCaption;
        }

        public Episode(int seasonNumber, int episodeNumber, string episodeName,
            DateTime firstAired, string overview, string url, string urlCaption, int confidence)
        {
            mSeasonNumber = seasonNumber;
            mEpisodeNumber = episodeNumber;
            mEpisodeName = episodeName;
            mFirstAired = firstAired;
            mOverview = overview;
            mURL = url;
            mURLCaption = urlCaption;
            mConfidence = confidence;
        }

        public int SeasonNumber
        {
            get { return mSeasonNumber; }
        }
        public int EpisodeNumber
        {
            get { return mEpisodeNumber; }
        }
        public string EpisodeName
        {
            get { return mEpisodeName; }
        }
        public DateTime FirstAired
        {
            get { return mFirstAired; }
        }
        public string Overview
        {
            get { return mOverview; }
        }
        public string URL
        {
            get { return mURL; }
        }
        public string URLCaption
        {
            get { return mURLCaption; }
        }
        public int Confidence
        {
            get { return mConfidence; }
        }
    }

    // Enumerator class
    public class MySeriesEnumerator
    {
        int nIndex;
        SeriesList collection;

        public MySeriesEnumerator(SeriesList coll)
        {
            collection = coll;
            nIndex = -1;
        }

        public bool MoveNext()
        {
            nIndex++;
            return (nIndex < collection.Count);
        }

        public Series Current
        {
            get
            {
                return (collection.Item(nIndex));
            }
        }
    }
}
